package com.stars.easyms.dubbo.filter;

import com.stars.easyms.base.alarm.EasyMsAlarmAssistor;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.bean.EasyMsRequestEntity;
import com.stars.easyms.base.trace.EasyMsTraceSynchronizationManager;
import com.stars.easyms.base.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.springframework.util.StringUtils;

import java.lang.reflect.UndeclaredThrowableException;

import static com.stars.easyms.base.constant.EasyMsCommonConstants.ASYNC_ID_CONNECTION_SYMBOL;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;

/**
 * <p>className: EasyMsDubboProviderFilter</p>
 * <p>description: dubbo的提供者端过滤器</p>
 *
 * @author guoguifang
 * @version 1.7.0
 * @date 2020/11/13 2:06 下午
 */
@Slf4j
@Activate(group = PROVIDER)
public class EasyMsDubboProviderFilter extends BaseEasyMsDubboFilter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) {
        // 获取接口类型
        String serviceName = invocation.getServiceName();
        if (isDubboInnerService(serviceName)) {
            return invoker.invoke(invocation);
        }

        // 获取接收请求时间
        long receiveRequestTime = System.currentTimeMillis();
        String receiveRequestTimeStr = DateTimeUtil.getDatetimeNormalStrWithMills(receiveRequestTime);
        String responseSys = SpringBootUtil.getApplicationName();
        String requestPath = null;
        String requestSys = null;
        String asyncId = null;
        String requestTime = null;
        String requestId;
        EasyMsDubboRequestInfo easyMsDubboRequestInfo = (EasyMsDubboRequestInfo) invocation.getObjectAttachment(HttpHeaderConstants.DUBBO_REQUEST_INFO);
        if (easyMsDubboRequestInfo != null) {
            requestPath = easyMsDubboRequestInfo.getRequestPath();
            requestSys = easyMsDubboRequestInfo.getRequestSys();
            requestId = easyMsDubboRequestInfo.getRequestId();
            asyncId = easyMsDubboRequestInfo.getAsyncId();
            requestTime = easyMsDubboRequestInfo.getRequestTime();
            if (StringUtils.isEmpty(requestId)) {
                requestId = TraceUtil.getTraceId();
            }
        } else {
            easyMsDubboRequestInfo = new EasyMsDubboRequestInfo();
            requestId = TraceUtil.getTraceId();
            easyMsDubboRequestInfo.setRequestId(requestId);
            invocation.setObjectAttachment(HttpHeaderConstants.DUBBO_REQUEST_INFO, easyMsDubboRequestInfo);
        }
        easyMsDubboRequestInfo.setReceiveRequestTime(receiveRequestTime);

        // 获取traceId
        String traceId = (String) invocation.getObjectAttachment(HttpHeaderConstants.TRACE_KEY);
        if (StringUtils.isEmpty(traceId)) {
            traceId = TraceUtil.getTraceId();
            invocation.setObjectAttachment(HttpHeaderConstants.TRACE_KEY, traceId);
        }

        // 将traceId和requestId放入日志线程本地变量中
        EasyMsTraceSynchronizationManager.setTraceInfo(traceId, requestId);
        if (asyncId != null) {
            asyncId = asyncId + ASYNC_ID_CONNECTION_SYMBOL + SpringBootUtil.getApplicationName();
            EasyMsTraceSynchronizationManager.setAsyncId(asyncId);
        }

        // 记录调用信息
        log.info("[接收服务-请求]-[接口类型: {}]-[请求系统: {}]-[服务系统: {}]-[链路Id: {}]-[请求ID: {}]{}-[发起请求时间: {}]-[接收请求时间: {}]{}.",
                TraceUtil.withUnknown(requestPath), TraceUtil.withUnknown(requestSys), responseSys, traceId, requestId,
                TraceUtil.getAsyncIdTrace(asyncId), TraceUtil.withUnknown(requestTime), receiveRequestTimeStr,
                easyMsDubboProperties.isLogRequest() ? "-请求数据: " + JsonUtil.toJSONString(invocation.getArguments()) : "");

        // 封装请求参数
        EasyMsRequestEntity requestEntity = new EasyMsRequestEntity();
        requestEntity.setRequestPath(requestPath);
        requestEntity.setTraceId(traceId);
        requestEntity.setRequestSys(requestSys);
        requestEntity.setRequestId(requestId);
        requestEntity.setAsyncId(asyncId);
        requestEntity.setRequestTime(requestTime);
        requestEntity.setReceiveRequestTime(receiveRequestTime);
        requestEntity.setReceiveRequestTimeStr(receiveRequestTimeStr);

        // 将requestEntity放入本地变量中
        EasyMsTraceSynchronizationManager.setRequestEntity(requestEntity);

        // 往下继续执行
        try {
            return invoker.invoke(invocation);
        } finally {
            clearTraceId();
        }
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        if (!isDubboInnerService(invocation.getServiceName())) {
            handleResponse(invocation, appResponse.getValue(), appResponse.getException());
        }
    }

    @Override
    public void onError(Throwable throwable, Invoker<?> invoker, Invocation invocation) {
        if (!isDubboInnerService(invocation.getServiceName())) {
            handleResponse(invocation, null, throwable);
        }
    }

    private void handleResponse(Invocation invocation, Object result, Throwable throwable) {
        String traceId = (String) invocation.getObjectAttachment(HttpHeaderConstants.TRACE_KEY);
        EasyMsDubboRequestInfo easyMsDubboRequestInfo =
                (EasyMsDubboRequestInfo) invocation.getObjectAttachment(HttpHeaderConstants.DUBBO_REQUEST_INFO);
        if (traceId == null || easyMsDubboRequestInfo == null) {
            return;
        }
        String requestPath = easyMsDubboRequestInfo.getRequestPath();
        String requestId = easyMsDubboRequestInfo.getRequestId();
        String asyncId = easyMsDubboRequestInfo.getAsyncId();
        String requestSys = easyMsDubboRequestInfo.getRequestSys();

        // 由于是异步，需要重新将traceId和requestId放入日志线程本地变量中
        EasyMsTraceSynchronizationManager.setTraceInfo(traceId, requestId);
        if (asyncId != null) {
            EasyMsTraceSynchronizationManager.setAsyncId(asyncId);
        }

        // 如果是未申报的异常类型则获取实际异常
        if (throwable instanceof UndeclaredThrowableException) {
            throwable = ((UndeclaredThrowableException) throwable).getUndeclaredThrowable();
        }

        // 获取返回响应时间及接口耗时
        long responseTime = System.currentTimeMillis();
        String responseTimeStr = DateTimeUtil.getDatetimeNormalStrWithMills(responseTime);
        Long costTime = responseTime - easyMsDubboRequestInfo.getReceiveRequestTime();

        // 记录响应信息
        if (throwable != null) {
            log.error("[接收服务-响应-异常]-[接口类型: {}]-[请求系统: {}]-[服务系统: {}]-[链路Id: {}]-[请求ID: {}]{}-[响应时间: {}]-[用时: {}ms]-异常信息: ",
                    requestPath, requestSys, SpringBootUtil.getApplicationName(), traceId, requestId,
                    TraceUtil.getAsyncIdTrace(asyncId), responseTimeStr, costTime, throwable);

            // 异常信息告警
            EasyMsAlarmAssistor.sendExceptionAlarmMessage(throwable);
        } else {
            log.info("[接收服务-响应]-[接口类型: {}]-[请求系统: {}]-[服务系统: {}]-[链路Id: {}]-[请求ID: {}]{}-[响应时间: {}]-[用时: {}ms]{}.",
                    requestPath, requestSys, SpringBootUtil.getApplicationName(), traceId, requestId,
                    TraceUtil.getAsyncIdTrace(asyncId), responseTimeStr, costTime,
                    easyMsDubboProperties.isLogResponse() ? "-响应数据: " + JsonUtil.toJSONString(result) : "");
        }

        // 如果接口用时超过预警值则告警
        if (costTime >= 3000) {
            String warnMessage = MessageFormatUtil.format("接口[{}]用时较长，共{}ms，请优化接口！", requestPath, costTime);
            log.warn(warnMessage);
            EasyMsAlarmAssistor.sendNormalAlarmMessage(warnMessage);
        }

        // 清除trace信息，该方法不会抛出异常，不需要使用finally
        clearTraceId();
    }

    private void clearTraceId() {
        EasyMsTraceSynchronizationManager.clear();
    }

}
